package snsdbook.engine;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.ArrayList;

import org.apache.commons.io.FileUtils;

import util.Util;

public class FileManager {
  
  private static final String kBiebox = "biebox";
  private static final String kLogFile = "selena.log";

  /**
   * Creates OR opens a repository directory/biebox.
   */
  public FileManager(String directory) {

    last_files_ = new File[0];
    directory_ = directory;
    if (!directory_.endsWith("/")) directory_ += "/";
    Util.Log("Checking that " + directory_ + " exists!");
    assert new File(directory_).exists();
    Util.Log("File Manager for " + directory + " is created");
    MakeDirExists(directory_ + kBiebox);
    
    Update();
  }
  
  /**
   * Moves all files from that FileManager to here.
   */
  public void MoveAll(FileManager that) {
    Util.Log("Attempting to move all files in " + that.directory() +
             " to " + directory());
    try {
      // Empty current directory
      FileUtils.deleteDirectory(new File(directory() + kBiebox));
      // Perform copy
      FileUtils.moveDirectoryToDirectory(new File(that.directory() + kBiebox),
                                         new File(directory()),
                                         /* create_dest_dir = */ false);
      // Finally, move the log if exists
      if (new File(that.directory() + kLogFile).exists()) {
        FileUtils.moveFile(new File(that.directory() + kLogFile),
                           new File(directory() + kLogFile));
      }
    } catch (Exception e) {
      Util.Fatal(e);
    }
    
    Util.Log("Move succesful");
    
    Update();
  }

  /**
   * Creates the given absolute directory if not yet exists.
   * Supports recursive creation. That is, a/b/c, if all didn't exist.
   * 
   * The first operand should be "
   */
  public static void MakeDirExists(String dirs) {
    if (new File(dirs).exists()) return;
    if (new File(dirs).mkdirs()) {
      Util.Log("Creation of directories: " + dirs + " successful");
    } else {
      Util.Fatal("Unable to create directories: " + dirs);
    }
  }
  
  /**
   * Returns a list of all files rooted here.
   */
  public File[] AllFiles() {
    FileAccumulatorVisitor vis = new FileAccumulatorVisitor();
    try {
      Files.walkFileTree(new File(directory() + kBiebox).toPath(), vis);
    } catch (Exception e) {
      Util.Fatal(e);
    }
    return vis.list();
  }
  
  /**
   * Updates the list of deleted files, and also saves currently available files
   */
  public void Update() {
    File[] current_files = AllFiles();
    
    // Find any files that are deleted
    for (File f : last_files_) {
      boolean found = false;
      for (File g : current_files) if (g.getAbsolutePath().equals(f.getAbsolutePath())) {
        found = true;
      }
      if (!found) {
        AddDeletedFileInfo(Relativize(f.getAbsolutePath()));
      }
    }
    
    last_files_ = current_files;
  }
  
  /**
   * Relativizes file path to here
   */
  public String Relativize(String abspath) {
    String base = directory_ + kBiebox;    
    return new File(base).toURI().relativize(
        new File(abspath).toURI()).getPath();
  }
  
  /**
   * Adds file X to the list of deleted files and stamp it with current time.
   */
  public void AddDeletedFileInfo(String rel_filepath) {
    Util.Log("File " + rel_filepath + " was deleted. Adding it to log...");
    
    File log = new File(directory() + kLogFile);
    if (!log.exists()) {
      try {
        Util.Log("Log file not exists, creating one...");
        FileUtils.touch(log);
      } catch (Exception e) {
        Util.Fatal(e);
      }
    }
    
    ArrayList<String> texts = new ArrayList<String>();
    texts.add(rel_filepath);
    texts.add(String.valueOf(System.currentTimeMillis()));
    Util.Log("Appending log...");
    try {
      FileUtils.writeLines(log, texts, /* append = */ true);
    } catch (Exception e) {
      Util.Fatal(e);
    }
    Util.Log("Log appended!");
  }
  
  /**
   * Retrieves list of deleted files. Given as a String[] of alternating
   * filename and deletion time.
   */
  public String[] GetDeletedFiles() {
    if (!new File(directory() + kLogFile).exists()) {
      // No log is found
      return new String[0];
    } else {
      try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(
            new FileInputStream(new File(directory() + kLogFile))));
        ArrayList<String> strs = new ArrayList<String>();
        while (true) {
          String str = reader.readLine();
          if (str == null) break;
          if (str.equals("")) continue;
          strs.add(str);
        }
        String[] ret = new String[strs.size()];
        strs.toArray(ret);
        return ret;
      } catch (Exception e) {
        Util.Fatal(e);
      }
      return null;
    }
  }
  
  /**
   * Clears log of deleted files.
   */
  public void ClearLog() {
    Util.Log("Clearing log of deleted files...");
    new File(directory() + kLogFile).delete();
  }
  
  /**
   * Reads a file (relative)
   */
  public byte[] ReadFile(String rel_filename) {
    try {
      return FileUtils.readFileToByteArray(
          new File(directory() + kBiebox + "/" + rel_filename));
    } catch (IOException e) {
      Util.Fatal(e);
    }
    return null;
  }
  
  /**
   * Deletes a file (relative)
   */
  public void DeleteFile(String rel_filename) {
    if (new File(directory() + kBiebox + "/" + rel_filename).delete()) {
      Util.Log("File " + rel_filename + " deleted.");
    } else {
      Util.Warning("Attempt to delete + " + rel_filename + " failed.");
    }
  }
  
  /**
   * Writes to a file
   */
  public void WriteToFile(String rel_filename, byte[] data, long timestamp) {
    // Create the directory containing the file
    File f = new File(directory() + kBiebox + "/" + rel_filename);
    String dir = f.getParent();
    if (dir != null) MakeDirExists(dir);
    Util.Log("Writing new data to " + rel_filename);
    try {
      FileUtils.writeByteArrayToFile(f, data);
      f.setLastModified(timestamp);
    } catch (IOException e) {
      Util.Fatal(e);
    }
  }
  
  /**
   * Get last modified time
   */
  public long GetModified(String rel_filename) {
    return new File(directory() + kBiebox + "/" + rel_filename).lastModified();
  }
  
  public String directory() { return directory_; }
  private String directory_;
  private File[] last_files_;
}
